
import java.util.ArrayList;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.CircleDef;
import org.jbox2d.collision.PolygonDef;
import org.jbox2d.collision.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.common.XForm;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;

import pulpfizz.physics.Actor;
import pulpfizz.physics.ActorType;
import pulpfizz.physics.ContactData;
import pulpfizz.physics.ContactEventListener;
import pulpfizz.physics.NewWorld;
import pulpfizz.physics.ShapeData;
import pulpfizz.pulp.body.PhysicsLayer;
import pulpfizz.pulp.body.Java2DBodySprite;

/**
 * Handles everything about the playing field creation.
 * Basically a glorified capsule edge tracer, but also
 * handles sensor placement and other good stuff.
 * <p>
 * Capsule is horizontally aligned - if you want vertical
 * alignment, rotate the body itself after creation.
 * </p>
 * 
 * @author eric
 */
public class FireField extends Actor {
	static private final float k_default_restitution = 0.4f;
	static private final float k_default_friction = 0.3f;
	static private final float k_default_thickness = 0.2f;
	/** Number of segments to decompose each half-circle into. */
	static private final int k_circle_segments = 20;
	/** 
	 * Body's center position (corresponds to m_body.getXForm().position).
	 * This is the center point on the playing field.
	 * Use setter to change and changes will show up in body.
	 */
	private Vec2 m_center;
	/** AABB width of playing field capsule. */
	private float m_width;
	/** AABB height of playing field capsule. */
	private float m_height;
	/** Thickness of walls (not half-thickness). */
	private float m_thickness;
	private float m_restitution;
	private float m_friction;
	private Body m_body;
	private World m_world;
	private ArrayList<Shape> m_shapes;
	private boolean isInitialized = false;
	
	private int ammoLeft;
	private int ammoRight;

    private float lifeLeft;
    private float lifeRight;

    private PhysicsLayer physics;
	
	public void setPhysicsLayer(PhysicsLayer pl) {
		physics = pl;
	}

    public float getLifeL() {
        return lifeLeft;
    }

    public float getLifeR() {
        return lifeRight;
    }

    public float subtractFromLifeL(float toSubtract) {
        lifeLeft -= toSubtract;
        return lifeLeft;
    }

    public float subtractFromLifeR(float toSubtract) {
        lifeRight -= toSubtract;
        return lifeRight;
    }

    public void setLifeL(float life) {
        lifeLeft = life;
    }

    public void setLifeR(float life) {
        lifeRight = life;
    }

    public int getAmmoL() {
		return ammoLeft;
	}
	
	public int getAmmoR() {
		return ammoRight;
	}
	
	public void incrementAmmoL() {
		++ammoLeft;
	}
	
	public void incrementAmmoR() {
		++ammoRight;
	}
	
	public void decrementAmmoL() {
		ammoLeft = Math.max(--ammoLeft,0);
	}
	
	public void decrementAmmoR() {
		ammoRight = Math.max(--ammoRight,0);
	}
	
	public void setAmmoL(int n) {
		ammoLeft = n;
	}
	
	public void setAmmoR(int n) {
		ammoRight = n;
	}
	
	public FireField(AABB aabb, World world) {
		this(aabb.upperBound.x - aabb.lowerBound.x,aabb.upperBound.y - aabb.lowerBound.y,
				new Vec2(aabb.lowerBound.x + (aabb.upperBound.x - aabb.lowerBound.x)*.5f,
				aabb.lowerBound.y + (aabb.upperBound.y - aabb.lowerBound.y)*.5f),
				world);
	}
	
	public FireField(float width, float height, Vec2 center, World world) {
		if (width <= height) throw new IllegalArgumentException("Playing field width must be greater than playing field height.");
		setWidth(width);
		setHeight(height);
		setCenter(center);
		setRestitution(k_default_restitution);
		setFriction(k_default_friction);
		setThickness(k_default_thickness);
		m_world = world;
		m_shapes = new ArrayList<Shape>();
		initFireField();
		this.setName("Playing field boundary");
		this.setType(ActorType.WALL);
		setSensors();
	}
	
	private float collectionRadius = 2.0f;
	private ShapeData collectorLeft, collectorRight;
	private PulpFireScene parent;
	static final int COLLECTOR = 3;
	private void setSensors() {
		CircleDef circle = new CircleDef();
		circle.isSensor = true;
		circle.radius = collectionRadius;
		circle.localPosition = getLeftEdge();
		Shape lCircle = m_body.createShape(circle);
		collectorLeft = new ShapeData();
		collectorLeft.addShape(lCircle);
		collectorLeft.setName("Left collector");
		collectorLeft.addToGroup(COLLECTOR);
        circle = new CircleDef();
		circle.isSensor = true;
		circle.radius = collectionRadius;
		circle.localPosition = getRightEdge();
		Shape rCircle = m_body.createShape(circle);
		collectorRight = new ShapeData();
		collectorRight.addShape(rCircle);
		collectorRight.setName("Right collector");
		collectorRight.addToGroup(COLLECTOR);
		try{
			NewWorld nw = (NewWorld)m_world;
			ContactEventListener collectionListener = new ContactEventListener() {
				public void trigger(ContactData c) {
					//System.out.println("hi");
					if ( ((Actor)c.shape1.getBody().getUserData()).isInGroup(PulpFireScene.BULLET_GROUP) ) {
						//System.out.println("Destroying body 1");
						physics.removeSpritesByBody(c.shape1.getBody());
						c.shape1.getBody().getWorld().destroyBody(c.shape1.getBody());
						float pan = 0.0f;
						if(((ShapeData)c.shape2.getUserData()) == collectorLeft) {
							incrementAmmoL();	
							pan = -1.0f;
						} else {
							incrementAmmoR();
							pan = 1.0f;
						}
						parent.slurpSound(1.0f,pan);
						parent.makeParticlesWorld(c.position, c.position, 40, 2.0f);
						
					} else if ( ((Actor)c.shape2.getBody().getUserData()).isInGroup(PulpFireScene.BULLET_GROUP) ) {
						//System.out.println("Destroying body 2");
						physics.removeSpritesByBody(c.shape2.getBody());
						c.shape2.getBody().getWorld().destroyBody(c.shape2.getBody());
						float pan = 0.0f;
						if(((ShapeData)c.shape1.getUserData()) == collectorLeft) {
							incrementAmmoL();	
							pan = -1.0f;
						} else {
							incrementAmmoR();
							pan = 1.0f;
						}
						parent.slurpSound(1.0f,pan);
						parent.makeParticlesWorld(c.position, c.position, 40, 2.0f);
					}
					//System.out.println(getAmmoL()+" "+getAmmoR());
				}
			};
			nw.getContactEventDispatcher().registerGroupListener(COLLECTOR, collectionListener);
		} catch (Exception e) {
			System.out.println("Failure");
		}
	}
	
	private void createBody() {
		BodyDef bd = new BodyDef();
		bd.userData = this;
		m_body = m_world.createBody(bd);
	}

	private void updateBodyPosition() {
		m_body.setXForm(m_center,m_body.getAngle());
	}

	/**
	 * Initializes the field's body and shapes.
	 * Requires m_width, m_height, and m_world to be set.
	 */
	private void initFireField() {
		m_shapes.clear();
		//Center is initialized as (0,0) - any center movement
		//is handled after the body is created.
		float circleRad = m_height * .5f;
		float rectWidth = m_width - m_height;
		float rectHeight = m_height;
		//Set rectangular piece corners
		Vec2 ulCorner = new Vec2(-rectWidth*.5f,rectHeight*.5f);
		Vec2 urCorner = new Vec2(-ulCorner.x, ulCorner.y);
		Vec2 lrCorner = new Vec2(urCorner.x,-ulCorner.y);
		Vec2 llCorner = new Vec2(ulCorner.x,lrCorner.y);
		ArrayList<Vec2> points = new ArrayList<Vec2>(2*k_circle_segments+4);
		points.add(ulCorner);
		points.add(urCorner);
		//Create right semicircle
		Vec2 rightCenter = new Vec2(urCorner.x,0.0f);
		for (int i=1; i<k_circle_segments; ++i) {
			float angle = (float)((Math.PI * i)/k_circle_segments);
			float x = rightCenter.x + circleRad*(float)Math.sin(angle);
			float y = rightCenter.y + circleRad*(float)Math.cos(angle);
			points.add(new Vec2(x,y));
		}
		points.add(lrCorner);
		points.add(llCorner);
		//Create left semicircle
		Vec2 leftCenter = new Vec2(ulCorner.x,0.0f);
		for (int i=1; i<k_circle_segments; ++i) {
			float angle = (float)((Math.PI * i)/k_circle_segments);
			float x = leftCenter.x - circleRad*(float)Math.sin(angle);
			float y = leftCenter.y - circleRad*(float)Math.cos(angle);
			points.add(new Vec2(x,y));
		}
		//Simpler to add the first point again to complete circuit
		points.add(ulCorner);
		
		createBody();
		
		//Create the actual shapes
		for(int i=0; i<points.size()-1; ++i) {
			createQuadSegment(points.get(i),points.get(i+1),leftCenter,rightCenter,m_thickness);
		}
		
		//Move the whole contraption into position
		updateBodyPosition();
		
		//We're good to go
		isInitialized = true;
	}
	
	/**
	 * Creates a quad segment between the two points based
	 * on the current settings.
	 * <p>
	 * Not useful in general, because this has hard-coded handling
	 * so that the circle quads fill the outline space with thickness.
	 * In other words, the quads are not necessarily rectangles, they
	 * depend on the local position and radius settings.
	 * </p>
	 * <p>
	 * Also, this will not properly handle points not lying on circle
	 * boundaries, and assumes that the points are coming in clockwise -
	 * if this is not the case, the resulting polygons might not be ordered
	 * correctly.  Seriously, don't use this for anything,
	 * it's a one-shot special purpose function!
	 * @param pA
	 * @param pB
	 */
	private void createQuadSegment(Vec2 pA, Vec2 pB, Vec2 leftCenter, Vec2 rightCenter, float thickness) {
		//Figure out what region the first point is in.
		boolean aIsLeft = (pA.x < 0.0f);
		boolean bIsLeft = (pB.x < 0.0f);
		Vec2 aDiff = pA.sub( (aIsLeft?leftCenter:rightCenter) );
		Vec2 bDiff = pB.sub( (bIsLeft?leftCenter:rightCenter) );
		aDiff.normalize();
		bDiff.normalize();
		aDiff.mulLocal(0.5f);
		bDiff.mulLocal(0.5f);
		PolygonDef pd = new PolygonDef();
		pd.friction = m_friction;
		pd.restitution = m_restitution;
		Vec2 v0 = pA.add(aDiff);
		Vec2 v1 = pA.sub(aDiff);
		Vec2 v2 = pB.sub(bDiff);
		Vec2 v3 = pB.add(bDiff);
		pd.vertices.add(v0);
		pd.vertices.add(v1);
		pd.vertices.add(v2);
		pd.vertices.add(v3);
		m_shapes.add(m_body.createShape(pd));
	}

	public void setWorld(World m_world) {
		this.m_world = m_world;
	}

	public World getWorld() {
		return m_world;
	}

	public void setBody(Body m_body) {
		this.m_body = m_body;
	}

	public Body getBody() {
		return m_body;
	}

	public void setFriction(float m_friction) {
		this.m_friction = m_friction;
	}

	public float getFriction() {
		return m_friction;
	}

	public void setRestitution(float m_restitution) {
		this.m_restitution = m_restitution;
	}

	public float getRestitution() {
		return m_restitution;
	}

	/**
	 * Sets field height.  If changed and field is already
	 * set up, reinitializes so that the change shows up.
	 * @param height
	 */
	public void setHeight(float height) {
		if (m_width <= height) throw new IllegalArgumentException("Playing field width must be greater than playing field height.");
		boolean diff = (this.m_height != height);
		this.m_height = height;
		if (isInitialized && diff) initFireField();
	}

	public float getHeight() {
		return m_height;
	}

	/**
	 * Sets field width.  If changed and field is already
	 * set up, reinitializes so that the change shows up.
	 * @param width
	 */
	public void setWidth(float width) {
		if (width <= m_height) throw new IllegalArgumentException("Playing field width must be greater than playing field height.");
		boolean diff = (this.m_width != width);
		this.m_width = width;
		if (isInitialized && diff) initFireField();
	}

	public float getWidth() {
		return m_width;
	}

	public void setThickness(float thickness) {
		if (thickness <= 0.0f) throw new IllegalArgumentException("Wall thickness must be greater than 0.");
		boolean diff = (this.m_thickness != thickness);
		this.m_thickness = thickness;
		if (isInitialized && diff) initFireField();
	}

	public float getThickness() {
		return m_thickness;
	}
	
	public void setCenter(Vec2 center) {
		m_center = center.clone();
		if (isInitialized) updateBodyPosition();
	}
	
	/** Returns a clone of the center position. */
	public Vec2 getCenter() {
		return m_body.getPosition();
	}
	
	public Vec2 getLeftEdge() {
		return m_body.getWorldPoint(new Vec2(-m_width*.5f,0.0f));
	}
	
	public Vec2 getRightEdge() {
		return m_body.getWorldPoint(new Vec2(m_width*.5f,0.0f));
	}
	
	public Vec2 getLeftSemiCenter() {
		return m_body.getWorldPoint(new Vec2(-m_width*.5f + m_height*.5f,0.0f));
	}
	
	public Vec2 getRightSemiCenter() {
		return m_body.getWorldPoint(new Vec2(m_width*.5f - m_height*.5f,0.0f));
	}

	public void setParent(PulpFireScene pulpFireScene) {
		parent = pulpFireScene;
	}
	
	final static float fieldSlopeForce = 0.5f;
	public void applyForceField(Body b) {
		if ( !((Actor)b.getUserData()).isInGroup(PulpFireScene.BULLET_GROUP) ) {
			return;
		}
		Vec2 bLocal = m_body.getLocalPoint(b.getWorldCenter());
		b.applyForce(new Vec2(fieldSlopeForce*bLocal.x,0.0f), b.getWorldCenter());
	}
	
}
